מבוא למחשב בשפת C הרצאה 5 : לולאות מבוססעלהשקפיםשחוברוע"ישיארצי, גיתיתרוקשטיין, איתןאביאור וסאהראסמירעבורהקורס "מבואלמדעיהמחשב". נכתב על-ידי טל כהן, נערך ע"י איתן אביאור. כלהזכויותשמורותלטכניון מכוןטכנולוגילישראל
לולאות קטעקודהמתבצעמספרפעמיםנקרא מספרהחזרותעשוילהיות: קבוע, משתנהאךנקבעלפניתחילתהלולאה, משתנהתוךכדיביצועהלולאה, בלתימוגבל. לולאה.(loop) כלחזרהעלביצועהלולאהנקראתמחזור.(iteration) בשפתC קיימיםשלושהפסוקיםהמאפשריםביצועלולאות: פסוק while פסוק do-while פסוק for בשפתC, כוחםהחישובי שלשלושתסוגיהפסוקיםשווה. בכלמקרהאנונבחרלהשתמשבזהשנראהמתאיםיותרלחישובשלנו. 2
לולאת while הינה פסוק מהצורה: while ( expression ) statement expression 0 ערכושלהביטוי expression נבדקשובושוב, לפניכלביצועשלהפסוק.statement 0 statement כלעודערך-האמתשלהביטוי expression אמת, יתבצעהפסוק statement שובושוב. הינו אםכברמלכתחילהערך-האמתשלהביטוי statement הינו שקר, הפסוק expression לאיתבצעכלל. 3
דוגמה 1: חישוב עצרת int n; int fact = 1; int i = 1; scanf("%d", &n); while ( i <= n ) { fact *= i; i++; printf("%d! = %d", n, fact); פעולת העצרת מוגדרת כדלקמן: n n! = i= i= 1 1 2 K n להלןקטעתוכניתהקוראת מספרמהקלט, מחשבתאת העצרתשלו, ומדפיסהאת התוצאה: 4
דוגמה 2: חישוב ממוצע ניתוח ותכנון קלט: סדרה של מספרים שלמים חיוביים ובסיומם המספר 0. פלט: ממוצע המספרים בסדרה (לא כולל ה- 0 ). הנחות: הקלטחוקי: מכילה רק מספרים שלמים חיוביים, הסדרה ישלפחותמספראחדבסדרה (מלבדה- 0 המצייןאתהסוף). אלגוריתם: ספירתמספרהמספרים, וחישובסכומםבאופןאיטרטיבי (עלידי לולאה). לאחרסיוםהלולאה: הסכוםמחולקבמספרהמספרים = הממוצע. 5
דוגמה 2: חישוב ממוצע מימוש #include <stdio.h> int main() { int value; int sum = 0; int num = 0; printf("please insert a natural number: "); scanf("%d", &value); while ( value ) { sum += value; num ++; printf("please insert a natural number: "); scanf("%d", &value); printf("\nthe mean of the %d numbers is %.2f.\n", num, (double)sum / num); return 0; 6
לולאת do-while statement 0 expression 0 הינהפסוקמהצורה: do statement while ( expression ); ערכושלהביטוי expression נבדקשובושוב, לאחרכלביצועשלהפסוק.statement כלעודערך-האמתשלהביטוי expression הינו אמת, יתבצעהפסוק statement שובושוב. אפילואםכברמלכתחילהערך-האמתשלהביטוי statement הינו שקר, הפסוק expression יתבצע (לפחותפעםאחת). 7
דוגמה 2: חישוב ממוצע מימוש נוסף #include <stdio.h> int main() { int value; int sum = 0; int num = 0; פסוקי הקלט/פלט מופיעים רק פעם אחת בתוכנית do { printf("please insert a natural number: "); scanf("%d", &value); if ( value > 0 ) { sum += value; num ++; while ( value ); printf("\nthe mean of the %d numbers is %.2f.\n", num, (double)sum / num); return 0; 8
לולאת for for ( exp 1 ; exp 2 ; exp 3 ) הינהפסוקמהצורה: statement exp 1 exp 2 0 statement exp 3 0 בתחילהמחושב exp 1 (פעםאחתבלבד). אחרכךמתחילביצועהלולאה, בדומהללולאת :while נבדקשובושוב, לפניכלביצועשלהפסוק ערכושלהביטוי exp 2.statement הינו אמת, יתבצעהפסוק exp 2 כלעודערך-האמתשלהביטוי שובושוב. statement הינו שקר, אםכברמלכתחילהערך-האמתשלהביטוי exp 2 לאיתבצעכלל. הפסוק statement בנוסףלכך, הביטוי exp 3 מחושבשובושוב, לאחרכלביצוע שלהפסוק statement (לפניהבדיקההחוזרתשל.(exp 2 כל אחד משלושת הביטויים יכול להיות ריק (בלי תלות בחבריו): אםהביטוי expו/או 1 expריקים, 3 איןמהלחשבאתערכם. אםהביטוי expריק, 2 ערכו אמת (שונהמלולאת.(while 9
לולאת for דוגמאות int n, i, fact; scanf("%d", &n); חישוב עצרת: fact = 1; for ( i = 1; i <= n; i++ ) fact *= i; printf("%d! = %d", n, fact); int sum, i; sum = 0; for ( i = 1; i <= n; i++ ) sum += i * i; n : i= 1 i 2 חישוב הביטוי 10
דוגמה 3: מציאת מינימום יש למצוא את המספר הקטן ביותר, מתוך סדרת מספרים נתונה באורךn. נניחכיהערךשל n נקלט (אוחושב) לפני-כן. ניסיון ראשון: int min = 0; int i; int num; for ( i = 0; i < n; i++ ) { scanf("%d", &num); if ( min > num ) min = num; /* Found new minimum */ מה הבעיה בגרסה זו? 11
דוגמה 3: מציאת מינימום תיקון הבעיה בגרסה הראשונה הייתה שהמשתנה min אותחל ל- 0. אםכלהמספריםבסדרהחיוביים, נקבלמינימוםשל 0 במקוםאתהמספר הקטןבסדרה. int min; int i; int num; ניסיון שני: scanf("%d", &min); for ( i = 1; i < n; i++ ) { scanf("%d", &num); if ( min > num ) min = num; /* Found new minimum */ 12
דוגמה 4: אלגוריתם לבדיקת ראשוניות.2, 3,, n 1 בהינתןשלםחיובי n, ישלקבועהאםהואראשוני. n איננוראשוניאםורקאםישל- n מחלקבתחום: int is_prime = 1; int n, i; scanf("%d", &n); if ( n == 1 ) { is_prime = 0; else { for ( i = 2; i < n; i++ ) if ( n % i == 0) is_prime = 0; 13
בדיקת ראשוניות: הצעות ייעול #include <math.h>... int is_prime = 1; int n, i;... מספיקלבדוקמחלקיםאפשרייםעדשורש.( n ) n לאחרשמסתברש- n אינומתחלקב- 2, כלומרהואאי-זוגי, ניתןלדלגעלבדיקתשארהמחלקיםהזוגיים. בלי שורה זו, התוכנית תתורגם ותתבצע, אבל התוצאה תהיה שגויה! if ( n == 1 ( n!= 2 && n % 2 == 0 ) ) { is_prime = 0; else { int sqrt_n = (int) sqrt( n + 0.5 ); for ( i = 3; i <= sqrt_n; i += 2 ) if ( n % i == 0 ) is_prime = 0; איזהחוסריעילותנותר כאןבכלזאת? 14
דיוק בבדיקת תנאי הלולאה האם שתי הלולאות הבאות מבצעות אותה פעולה? for ( x = 0.0; x!= 3.0; x += 1.0/7.0 ) { for ( x = 0.0; x < 3.0; x += 1.0/7.0 ) { בגללחוסרהדיוקבייצוגמספריםממשיים, אנועלולים "לפספס" את הגבולהמדויקשלסיוםהלולאה. עלמנתלוודאשאנועוצריםאתהלולאה, לאנבדוקהאם "הגענולגבול", אלאהאם "לאחרגנומהתחום": > >= <= < נשתמשבפעולותהיחס:!= == ולאבפעולותההשוואה: 15
שקילות בין סוגי הלולאות בשפת,C שלושתסוגיהלולאות while for do-while שקולים. כלמהשניתןלעשותבעזרתסוגאחד, ניתןלעשותבעזרתשניהסוגים האחרים. למשל, החלפתלולאת while בלולאת :do-while פעם אחת. יבוצע statement ראשית, אחר-כך, נבדוקאת,cond ואםהואמתקיים, נבצעשובאת.statement ושובנבדוקאת,cond ואםהואעדיןמתקיים שובנבצעאת,statement וכןהלאה..1.2.3 do { statement; while (cond); בדיוק אותו דבר! statement; while (cond) { statement; 16
שקילות בין סוגי הלולאות מורה נבוכים שארהשקילויותתוצגנהבשיעוריהתרגול. באיזהסוגלולאהלבחור? תלוימהאנורוציםלעשות! למשל: אםברורשהפעולה בתוך הלולאה צריכה להתבצע לפחות פעםאחת, טבעילהשתמשב- do-while. אםייתכןשהפעולהלאתתבצעכלל, עדיףלהשתמשב-.while אםללולאהישהתחלה, התקדמותוקו-סיוםברורים, עדיף להשתמשב- for. 17
פסוקי continue ו- break אלההםפסוקיםמיוחדיםלצורךשבירתרצףהביצועשללולאה. continue מפסיקאתביצועהאיטרציההנוכחיתשלהלולאה, ומתחילמיידאתביצוע האיטרציההבאה. בלולאת for ביטויהקידום ) 3 (exp יבוצעבכלזאת לפניהמעבר לאיטרציההבאה. break מפסיקאתביצועהלולאה, ומדלגמיידלפסוקהבאלאחרהלולאה. כזכור, פסוק break משמשגםבתוךמבנה switch גםשםהוא מפסיקאתפעולתהמבנה, ומדלגמיידלפסוקהבאלאחריו. 18
for ( i = 0; i < 100; i++ ) { scanf("%d",&num); if ( num > 0 ) sum += num; continue דוגמה לשימוש ב- continue המשימה: קרא סדרה של 100 מספרים מהקלט, חשב את הסכום של המספרים החיוביים שבסדרה. for ( i = 0; i < 100; i++ ) { scanf("%d",&num); 19 שניהקטעיםשקולים. השימושב- continue נוח יותר כאשר: ההמשךארוך, במיוחדאםישבהמשךעודועודסיבותלפסוקי נוספים. if ( num <= 0 ) continue; sum += num;
דוגמה לשימוש ב- break המשימה: קרא סדרה של עד 100 מספרים חיוביים מהקלט חשב את הסכום של המספרים החיוביים שבסדרה. (עצור כשמופיע מספר שלילי), num = 0; for ( i = 0; i < 100 && num >= 0; i++ ) { scanf("%d",&num); for ( i = 0; i < 100; i++ ) { if ( num > 0 ) scanf("%d",&num); sum += num; if ( num <= 0 ) break; sum += num; שניהקטעיםשקולים. השימושב- break נוחיותר: פסוקה- for נקייותר, ההמשךעשוילהיותארוך, עשויותלהיותבהמשךעודועודסיבותלפסוקי break נוספים. 20
לולאות אינסופיות לולאהאינסופיתהיאלולאה (מכלסוג) בהערךהביטויהנבדקהינו תמיד אמת (שונהמ- 0 ). התוכניתנתקעתבתוךהלולאה, לאמתקדמת, ולאמסתיימת. שגיאותתכנותעלולותלגרוםבטעותללולאהאינסופית. במקרהכזהאיןמנוסמעצירתהתוכניתמבחוץ: באמצעותמערכתההפעלה ((אםמתאפשר), ע"י הורדת מערכת ההפעלה באופן מבוקר ע"י כיבוי המחשב (במקרים עוד יותר חמורים). (במקרים חמורים), ניתן לייצר לולאות אינסופיות במכוון, { ) 1 while( לדוגמה: for( ; ; ) { במידתהצורך, היציאהמלולאהאינסופיתמכוונתתיעשהבאמצעות פסוק break (אופסוק.(return 21
קצת מתורת המספרים... משפט "אלגוריתםהחילוק" (החלקי) קובעכילכלשנימספריםטבעיים: d 0 n, קיימיםשנימספריםטבעיים r q, כךש: n = q d + r מתקיים: r < d 0 מתקיים: ישרקזוגאחד q ו- r המקייםאתשניהתנאיםהללו. המספריםהמשתתפיםנקראיםבשמות: (dividend) מחולק n (divisor) מחלק d (quotient) מנה q (remainder) שארית r ביצועהחישוב: d מ- n שובושוב (כלעודהיתרהאיננהשלילית). שיטה 1 : החסראת מספרהחיסוריםהואהמנה, והיתרההיאהשארית. בצעאלגוריתםחילוקמהיר, למשל "חילוקארוך" שלומדיםבביה"ס. שיטה 2 : ניתןלהרחיבאתהמשפטגםלמספריםשליליים. 22
עוד קצת מתורת המספרים....m אםבחלוקתn ב- d השארית (r) שווהל- 0, נאמרכי d מחלק את n. לכלמספרטבעיגדולמ- 1 ישלפחותשנימחלקים: 1, והואעצמו. מספרטבעיגדולמ- 1 שאיןלומחלקיםנוספיםנקראראשוני. d נקרא מחלקמשותף של n ו- m אםורקאםהואמחלקגםאת n וגםאת למה 1 : d עשוילחלקאת n רקאם.d n למה 2: מחלקמשותףשל n ו- m חייבלהיותקטןאושווהלשניהמחולקיםגםיחד. למה 3 : לכלשנימספריםטבעיים n ו- m ישלפחותמחלקמשותףאחד: 1. מסקנה: לכלשנימספריםטבעיים n ו- m ישמחלקמשותףמקסימאלי. המחלקהמשותףהגדולביותר Divisor) (Greatest Common טבעייםn ו- m מסומןע"י: (m.gcd(n, של שני מספרים 23
חשיבותו של המחלק המשותף הגדול ביותר (GCD) המחלקהמשותףהגדולביותרשלשנימספריםחשובבכמההקשרים, למשל: n n gcd( n, m) ע"מלצמצםבמהירותשבר: = m m gcd( n, m) ע"מלמצוא מכפלהמשותפתקטנהביותר (m,lcm(n, למשללצורך חיבורשברים: lcm( n, m) gcd( n, m) = n m a b + c d = lcm( b, d) a b lcm( b, d) + lcm( b, d) c d lcm( b, d) 24
חישוב המחלק המשותף הגדול ביותר (GCD) שיטת ביה"ס היסודי: פרקאתשניהמספרים n ו- m לגורמיםראשוניים. מכפלת הגורמיםהראשונייםהמשותפים היאה-( gcd(n,m. דוגמה: n = 1350 = 2 3 3 5 2 m = 700 = 2 2 5 2 7 gcd(1350, 700) = 2 5 2 = 50 מכפלת החיתוך שלסדרותהגורמיםהראשונייםהיאה- gcd. מכפלת האיחוד שלסדרותהגורמיםהראשונייםהיאה- lcd : lcd(1350, 700) = 2 2 3 3 5 2 7 = 18,900 gcd(1350, 700) lcd(1350, 700) = 2 3 3 3 5 4 7 = 945,000 25
פירוק מספר לגורמים ראשוניים המשפטהיסודישלהאריתמטיקהקובעכיכלמספרטבעיגדולמ- 1 יכוללהיכתבבצורהיחידהכמכפלהשלגורמיםראשוניים (מסודרים). אלגוריתםלפירוקמספרטבעי n (גדולמ- 1 ) לגורמיוהראשוניים: התחלעםיתרה m שערכהההתחלתיהוא n, וסדרתגורמיםריקה. נסהשובושוב: m לחלק את בכל אחד מהמספרים (הראשוניים) p אשר בתחום 2.. m אם m ב- p : אכןמתחלק p הוסף את חלק את היתרה לסדרת הגורמים, m = m / p (וגם תחום הסריקה 2.. m הוסף את היתרה m לסדרת הגורמים. קטן בהתאם). דוגמה עבור = 90 :n 90 = 2 45 45 = 3 15 15 = 3 5 5 = 5 קושי: האלגוריתםאינומעשיעבור n גדול. למשל, עבורמספרבן 18 ספרותעלול להיווצרהצורךלעבורעלכלהמספרים (הראשוניים) עד 1,000,000,000. מסיבהזו, פירוקמספרלגורמיםראשוניים משמשבסיסלקריפטוגרפיהמודרנית. 26
ועוד קצת מתורת המספרים... d,m,n עבור d אם ו- k מחלק את טבעיים הגדולים מ- 0 : מחלק את d אזי n n+d ואת.nd מחלק את d אזי n מחלק את d אם n+kd ואת.nkd אם d מחלקאת,nm ואת n+m m ואת n ולכןגםאת (הקטןאושווהל- n ), אזי.n % m d,m קבוצתהמחלקיםהמשותפיםשל n ושל m ושל שווהלקבוצתהמחלקיםהמשותפיםשל nm ולכן גם לקבוצת המחלקים המשותפים של ושל n % m מחלק את.m המחלקהמשותףהגדולביותרשל n ושל m שווהלמחלקהמשותףהגדולביותרשל nm m, ושל ולכן גם למחלק המשותף הגדול ביותר של n % m ושל.m gcd(n, m) = gcd(nm, m) = gcd(n % m, m) n > 0 gcd(n, n) = gcd(n, 0) = n 27
האלגוריתם של אוקלידס לחישוב GCD n הקלט: הפלט: שנימספריםטבעייםגדוליםמ- 0,.gcd(n,m) ו- m. שיטה 1 (האלגוריתםהמקורי): כלעוד n m חזור: אם n > m החלףאת n ואם m > n החלףאת m ב: ב:.nm.mn n = m ועל כן: אריסטו.gcd(n,m) = gcd(n,n) = n.(n > m שיטה 2 (מתבססתעלאלגוריתםחילוק): כלעוד > 0 m חזור: החלףאת n ב:.n % m החלףבין n ו- m (כךשיתקייםשוב m = 0 ועל כן:.gcd(n,m) = gcd(n,0) = n האלגוריתם היה כנראה ידוע ל-א וד וק ס וס מ-ק נ יד ס א ר יס ט ו א וק ל יד ס 375) (Εὔδοξος ὁ Κνίδιος) (Ἀριστοτέλης) רומז עליו בכתביו (Εὐκλείδης) הציג אותו ביצירתו 330) לפנה"ס). "יסודות" (ספר 7, משפט 300) (2 אוקלידס לפנה"ס). לפנה"ס). 28
הדגמת האלגוריתם של אוקלידס (בשיטה 2) 2 דוגמה 1 דוגמה m n n = q m + r n m 100 17 17 15 15 2 2 1 1 0 gcd(100, 17) = 1 1350 = 1 700 + 650 700 = 1 650 + 50 650 = 13 50 + 0 1350 700 700 650 650 50 50 0 gcd(1350, 700) = 50 שני מספרים שאין להם מחלק משותף הגדול מ- 1 זרים נקראים 29
קידוד האלגוריתם של אוקלידס #include <stdio.h> int main() { unsigned int n, m; unsigned int t; שיטה 1 (האלגוריתם המקורי) scanf("%u%u", &n, &m); if ( n == 0 && m == 0 ) { /* Error! */ while ( n!= m ) if ( n > m ) n -= m; else m -= n; while ( m > 0 ) { t = m; m = n % m; n = t; printf("the gcd is %u\n", n); return 0; שיטה 2 (שימוש באלגוריתם חילוק) 30